﻿using System;
using System.Linq;
using DNT.RavenQA.Core.Models;
using Rhino.Mocks;
using Xunit;

namespace DNT.RavenQA.Core.Tests.Models
{
    public class QuestionTests : TestBase
    {
        [Fact]
        public void EnsureJustNonEmptyTagsAreAdded()
        {
            var question = FakeQuestion();
            question.AddTags(new[] {"a", "b", String.Empty, " "});
            Assert.Equal(2, question.Tags.Count());
        }

        [Fact]
        public void AnswerCountIsIncremented()
        {
            var question = FakeQuestion();
            Assert.Equal(0, question.AnswersCount);
            question.AddAnswer(() =>
                                   {
                                       var answerInput = MockRepository.GenerateStub<IAnswerInput>();
                                       answerInput.Stub(i => i.Author).Return(FakeUser);
                                       answerInput.Stub(i => i.Question).Return(question);
                                       return Answer.New(answerInput, () => DummyDate);
                                   });
            Assert.Equal(1, question.AnswersCount);
        }

        [Fact]
        public void IncrementQuestionViews()
        {
            var question = FakeQuestion();
            var views = ContributeViews.New(question);
            Assert.Equal(0, views.TotalAmount);
            question.SetAsViewed(() => views, FakeUser);
            Assert.Equal(1, views.TotalAmount);

            question.SetAsViewed(() => views, FakeUser);
            Assert.Equal(1, views.TotalAmount);

            var newUser = MockRepository.GenerateStub<User>();
            newUser.Stub(i => i.Id).Return("users/2");
            question.SetAsViewed(() => views, newUser);
            Assert.Equal(2, views.TotalAmount);
        }

        [Fact]
        public void VoteUpIncrementTotalAmount()
        {
            VoteInternal((contribute, builder, user) => contribute.VoteUp(builder, user), 1);
        }

        [Fact]
        public void VoteDownDecrementTotalAmount()
        {
            VoteInternal((contribute, builder, user) => contribute.VoteDown(builder, user), -1);
        }

        public void VoteInternal(Action<Contribute, Func<ContributeVotes>, User> action, Int32 expected)
        {
            var question = FakeQuestion();
            var votes = ContributeVotes.New(question);
            Assert.Equal(votes.TotalAmount, 0);
            action(question, () => votes, FakeUser);
            Assert.Equal(expected, votes.TotalAmount);
        }

        [Fact]
        public void AlreadyVoted()
        {
            var question = FakeQuestion();
            var votes = ContributeVotes.New(question);
            question.VoteDown(() => votes, FakeUser);
            Assert.Throws<AlreadyVotedQuestionException>(() => question.VoteUp(() => votes, FakeUser));
        }

        [Fact]
        public void InvalidReferenceOnDifferentContributeVotesForSameContribute()
        {
            var question = FakeQuestion();

            var votes1 = MockRepository.GenerateStub<ContributeVotes>();
            votes1.Stub(v => v.Id).Return("votes/1");
            question.VoteDown(() => votes1, FakeUser);

            var votes2 = MockRepository.GenerateStub<ContributeVotes>();
            votes2.Stub(v => v.Id).Return("votes/2");
            Assert.Throws<InvalidReferenceException>(() => question.VoteDown(() => votes2, FakeUser));
        }

        [Fact]
        public void InvalidReferenceOnDifferentQuestionViewsForSameContribute()
        {
            var question = FakeQuestion();

            var views1 = MockRepository.GenerateStub<ContributeViews>();
            views1.Stub(v => v.Id).Return("views/1");
            question.SetAsViewed(() => views1, FakeUser);

            var views2 = MockRepository.GenerateStub<ContributeViews>();
            views2.Stub(v => v.Id).Return("views/2");
            Assert.Throws<InvalidReferenceException>(() => question.SetAsViewed(() => views2, FakeUser));
        }

        [Fact]
        public void EnsureDateUpdatedOnAnswer()
        {
            var question = FakeQuestion();
            var updateDate = new DateTime(2012, 2, 4, 3, 1, 3, 6);
            var input = new FakeAnswerInput
                            {
                                Author = FakeUser,
                                Body = "Hello!",
                                Question = question
                            };
            var answer = Answer.New(input, () => new DateTime(2010, 1, 2, 3, 4, 5, 6));
            question.AddAnswer(() => answer, () => updateDate);
            Assert.Equal(question.LastUpdateOn, updateDate);
        }

        [Fact]
        public void AnsweringAuthorization()
        {
            // TODO: Arrage phase is quite hard!
            var date = new DateTime(2012, 1, 3, 3, 1, 3, 6);
            var questionInput = new FakeQuestionInput
            {
                Author = FakeUser,
                Body = "Hello!",
                Title = "Fake title"
            };
            var question = new DummyQuestion(questionInput, () => date);
            question.SetId("questions-32");

            var unauthorizedUser = MockRepository.GenerateStub<User>();
            unauthorizedUser.Stub(i => i.Id).Return("users/98");

            var updateDate = new DateTime(2012, 2, 4, 3, 1, 3, 6);

            var answerInput = new FakeAnswerInput
            {
                Author = FakeUser,
                Body = "Hello!",
                Question = question
            };
            var answer = new FakeAnswer(answerInput, () => updateDate);
            answer.SetId("answers-837");

            Assert.Throws<NotAuthorizedException>(() => question.SetAsAnswered(answer, unauthorizedUser, () => updateDate));
            question.SetAsAnswered(answer, FakeUser, () => updateDate);
            Assert.Throws<AnswerAlreadyAcceptedException>(() => question.SetAsAnswered(answer, FakeUser, () => updateDate));
        }

        class FakeAnswerInput : IAnswerInput
        {
            public String Body { get; set; }
            public User Author { get; set; }
            public Question Question { get; set; }
        }

        private class FakeAnswer : Answer
        {
            public FakeAnswer(IAnswerInput input, Func<DateTime> now)
                : base(input, now) { }

            internal void SetId(String id)
            {
                Id = id;
            }
        }

        class FakeQuestionInput : IQuestionInput
        {
            public String Title { get; set; }
            public String Body { get; set; }
            public User Author { get; set; }
            public String[] Tags { get; set; }
        }

        private class DummyQuestion : Question
        {
            public DummyQuestion(IQuestionInput input, Func<DateTime> now)
                : base(input, now) { }

            internal void SetId(String id)
            {
                Id = id;
            }
        }
    }
}